﻿namespace FSpec

open System
open System.Linq
open System.ComponentModel 
open System.Collections.Generic
open System.Diagnostics 



type ShouldFragment() =
    inherit obj()

    member this.equal expected actual = 
        verify(fun()->
            if actual |> equals expected then
                null
            else
                createFailureFrom (Resources.TheValuesShouldBeEqual)
                |> addLabelledValues ("Expected", expected) ("Actual", actual)
                |> toFailureException
        )
                
    member this.contain expected actual =
        verify(fun()->
            if actual |> contains expected then
                null
            else
                createFailureFrom (format Resources.TheCollectionShouldContainTheElement_0_ [expected])
                |> addLabelledValues ("Collection", actual) ("Element", expected) 
                |> toFailureException
        )
            
    member this.containTheString (expected:string) (actual:string) =
        verify(fun()->
            if actual |> containsTheString expected then 
                null
            else
                createFailureFrom (format Resources.TheStringShouldContainTheSubstring_0_ [expected])
                |> addLabelledValues ("String", actual)("Substring", expected)
                |> toFailureException
        ) 
    
    member this.startWith (expected:string) (actual:string) =
        verify(fun()->
            if actual |> startsWith expected then
                null
            else
                createFailureFrom (format Resources.TheStringShouldStartWith_0_ [expected])
                |> addLabelledValues ("String", actual)("Substring", expected)
                |> toFailureException
        ) 
            
    member this.endWith (expected:string) (actual:string) =
        verify(fun()->
            if actual |> endsWith expected then
                null
            else
                createFailureFrom (format Resources.TheStringShouldEndWith_0_ [expected])
                |> addLabelledValues ("String", actual)("Substring", expected)
                |> toFailureException
        )
    

    member this.matchExpression (expected:string) (actual:string) =
        let options = System.Text.RegularExpressions.RegexOptions.None 
        verify(fun()->
            if actual |> matchExpression expected options then
                null
            else
                createFailureFrom (Resources.TheStringShouldMatchARegularExpression)
                |> addLabelledValues ("String", actual)("Regex", expected)
                |> toFailureException
        ) 
    
    member this.beTrue actual =
        verify(fun()->
            if actual |> isTrue then
                null
            else
                createFailureFrom (Resources.TheValueShouldBeTrue)
                |> addRawValue actual
                |> toFailureException
        )

    member this.beFalse actual =
        verify(fun()->
            if actual |> isFalse then
                null
            else
                createFailureFrom (Resources.TheValueShouldBeFalse)
                |> addRawValue actual
                |> toFailureException
        )

    member this.beGreaterThan expected actual  =
        verify(fun() ->
            if actual |> isGreaterThan expected then
                null
            else
                createFailureFrom Resources.TheValueShouldBeGreaterThanThis 
                |> addLabelledValues ("LHS", actual)("RHS", expected)
                |> toFailureException
        ) 
                 
    member this.beLessThan expected actual =
        verify(fun() ->
            if actual |> isLessThan expected then
                null
            else
                createFailureFrom Resources.TheValueShouldBeLessThanThis 
                |> addLabelledValues ("LHS", actual)("RHS", expected)
                |> toFailureException
        )
                
    member this.beLessThanOrEqualTo expected actual =
        verify(fun() ->
            if actual |> isLessThanOrEqualTo expected then
                null
            else
                createFailureFrom Resources.TheValueShouldBeLessThanOrEqualToThis 
                |> addLabelledValues ("LHS", actual)("RHS", expected)
                |> toFailureException
        )
        
    member this.beGreaterThanOrEqualTo expected actual =
        verify(fun() ->
            if actual |> isGreaterThanOrEqualTo expected then
                null
            else
                createFailureFrom Resources.TheValueShouldBeGreaterThanOrEqualToThis 
                |> addLabelledValues ("LHS", actual)("RHS", expected)
                |> toFailureException
        )
                
    member this.beTheSameAs expected actual =
        verify(fun() ->
            if actual |> isTheSameAs expected then
                null
            else
                createFailureFrom Resources.TheValuesShouldBeTheSame 
                |> addRawValues expected actual
                |> toFailureException
        )

    member this.beEmpty<'t> (collection:IEnumerable<'t>) =
        verify(fun() ->
            if collection |> isEmpty then 
                null
            else
                createFailureFrom Resources.TheCollectionShouldBeEmpty
                |> addRawValue collection    
                |> toFailureException
        )
    
    member this.throw (exceptionType:Type) (func:unit->unit) =
        verify(fun() ->
            if func |> throws exceptionType then
                null
            else   
                createFailureFrom (
                    format 
                        Resources.TheExpressionShouldThrowA_0_ 
                        [exceptionType]
                )
                |> toFailureException 
        )

    member this.failWith (message:string) (func:unit->'a) =
        verify(fun()-> 
            if func |> failsWith message then
                null
            else
                createFailureFrom (
                    format
                        Resources.TheExpressionShouldFailWithTheMessage_0_ 
                        [message]
                ) |> toFailureException
        )

    member this.beBetween lowerBound conjunction upperBound boundType value  =
        verify(fun() ->
            if value |> isBetween lowerBound conjunction upperBound boundType then null else

            let failure =
                match boundType with
                | Inclusive ->
                    createFailureFrom (format Resources.TheValueShouldBeWithin_0_And_1_Inclusive [lowerBound; upperBound]) 
                | Exclusive ->
                    createFailureFrom (format Resources.TheValueShouldBeWithin_0_And_1_Exclusive [lowerBound; upperBound])
            failure
                |> addLabeledValue ("Value", value)
                    |> addLabeledValue ("Lower bound", lowerBound)
                    |> addLabeledValue ("Upper bound", upperBound)
                    |> toFailureException
        )

    member this.inheritFrom (t:System.Type) value =
        verify(fun()->
            if t.IsAssignableFrom (value.GetType()) then
                null
            else
                createFailureFrom (format Resources.TheObjectShouldInheritFromType_0_ [t])
                |> toFailureException
        )

    member this.beWithin tolerance conjunction expectedValue value =
        verify(fun()->
            let deviation = abs(value - expectedValue)
            if value |> isWithin tolerance Of expectedValue then
                null
            else
                createFailureFrom (format
                                    Resources.TheValueShouldBeWithin_0_of_1_ 
                                    [tolerance;expectedValue])
                |> addLabeledValue("Expected", expectedValue)
                |> addLabeledValue("Actual", value)
                |> addLabeledValue("Tolerance", tolerance)
                |> addLabeledValue("Deviation From Expected", deviation)
                |> addLabeledValue("Deviation From Tolerance", abs(tolerance - deviation))
                |> toFailureException
        )

    member this.beA expectedType instance = 
        verify(fun()->
            if instance |> isA expectedType then
                null
            else
                createFailureFrom (format
                                    "The actual value was expected to be an instance of the type {0}"
                                    [expectedType])
                |> addLabelledValues("Actual", instance)("ExpectedType", expectedType)
                |> toFailureException
        )

    member this.beNull value = 
        verify(fun()->
            if value |> isNull then
                null
            else
                createFailureFrom(Resources.TheValueShouldBeNull)
                |> addLabeledValue ("Actual", value)
                |> toFailureException
        )

    member this.intersect list1 list2 =
        verify(fun()->
            if list1 |> intersects list2 then
                null
            else
                createFailureFrom ("Expected the lists to intersect.")
                |> addLabelledValues("List 1", list1)("List 2", list2)
                |> toFailureException
        )

    member this.ignoreCase = new IgnoreCaseFragment(true)
        
    //------- Cleaning up intellisense --------//
    interface IHideObjectMembers with
            override this.Equals(o:obj) = base.Equals(o)
            override this.GetHashCode() = base.GetHashCode()
            override this.ToString() = base.ToString()
            override this.GetType() = base.GetType()
            
    
    [<EditorBrowsable(EditorBrowsableState.Never)>]
    static member Equals(o1, o2) = Object.Equals(o1, o2)

    [<EditorBrowsable(EditorBrowsableState.Never)>]
    static member ReferenceEquals(o1, o2) = Object.ReferenceEquals(o1, o2)
    //-----------------------------------------//
